// <copyright file="ModbusTCPPacket.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Robotics.Hardware.Modbus
{
    using System;
    using System.Net.Sockets;
    using System.Runtime.Serialization;

    /// <summary>
    /// Common constants and methods for ModbusTCP packets
    /// </summary>
    public static class ModbusTCPPacket
    {
        /// <summary>
        /// Number of bytes in the transaction identifier
        /// </summary>
        public const int TransactionIDByteCount = 2;

        /// <summary>
        /// Number of bytes in the protocol identifier
        /// </summary>
        public const int ProtocolIdentifierByteCount = 2;

        /// <summary>
        /// Number of bytes in the length field
        /// </summary>
        public const int PacketLengthByteCount = 2;

        /// <summary>
        /// Number of bytes in the unit ID
        /// </summary>
        public const int UnitIDByteCount = 1;

        /// <summary>
        /// Number of bytes in the function ID
        /// </summary>
        public const int FunctionIDByteCount = 1;

        /// <summary>
        /// Number of bytes in the register address
        /// </summary>
        public const int RegisterAddressByteCount = 2;

        /// <summary>
        /// Total number of bytes in a read input registers packet
        /// </summary>
        public const int ReadInputRegistersByteCount = 12;

        /// <summary>
        /// Number of bytes in the bytes to follow value
        /// </summary>
        public const int BytesToFollowByteCount = 1;

        /// <summary>
        /// Number of bytes in a word
        /// </summary>
        public const int ByteCountPerWord = 2;

        /// <summary>
        /// Index (zero based) into a byte array representing a packet where the first
        /// byte of the packet length value will be found
        /// </summary>
        public const int PacketLengthValueStartIndex = 4;

        /// <summary>
        /// Length parameter for the read input registers packet
        /// </summary>
        public const ushort ReadInputRegistersLength = 6;

        /// <summary>
        /// Protocol identifier for Modbus TCP (0)
        /// </summary>
        public const ushort ModbusTCPProtocolID = 0;

        /// <summary>
        /// First byte of ModbusTCPProtocolID
        /// </summary>
        public const byte ModbusTCPProtocolIDFirstByte = 0;

        /// <summary>
        /// Second byte of ModbusTCPProtocolID
        /// </summary>
        public const byte ModbusTCPProtocolIDSecondByte = 0;

        /// <summary>
        /// Mask for the lowest byte of an unsigned short
        /// </summary>
        public const ushort LowestByteMask = 0x00FF;

        /// <summary>
        /// Determines whether the sequence of bytes in <c>data</c> constitute
        /// a complete ModbusTCP packet.
        /// </summary>
        /// <param name="data">Sequence of bytes representing a packet or partial packet.</param>
        /// <param name="numBytes">Number of valid bytes in <c>data</c> (size of data may 
        /// be greater than the number of valid bytes).  </param>
        /// <returns>True if the sequence of bytes in <c>data</c> represent an entire ModbusTCP packet. </returns>
        public static bool IsCompletePacket(byte[] data, int numBytes)
        {
            // return false if there are not enough bytes to get a length value
            if (numBytes < PacketLengthValueStartIndex + PacketLengthByteCount)
            {
                return false;
            }

            // need to swap byte order of length
            byte[] lengthBytes = new byte[2];
            lengthBytes[0] = data[PacketLengthValueStartIndex + 1];
            lengthBytes[1] = data[PacketLengthValueStartIndex];

            ushort length = BitConverter.ToUInt16(lengthBytes, 0);
            int expectedTotalLength = length + PacketLengthValueStartIndex + PacketLengthByteCount;

            if (numBytes == expectedTotalLength)
            {
                return true;
            }

            if (numBytes < expectedTotalLength)
            {
                return false;
            }

            // We've read more bytes than expected.  This should not happen.
            throw new ApplicationException(
                "Recieved unexepcted data over Modbus, was expecting " + expectedTotalLength + " but received " + numBytes);
        }

        /// <summary>
        /// Copies bytes from source unsigned short to destination, reversing their order.
        /// </summary>
        /// <param name="source">Source value.</param>
        /// <param name="destination">Destination array. </param>
        /// <param name="destinationStartIndex">Index where data is copied to.</param>
        public static void CopyBytesInReverseOrder(
          ushort source,
          byte[] destination,
          int destinationStartIndex)
        {
            destination[destinationStartIndex + 1] = (byte)(source & LowestByteMask);
            destination[destinationStartIndex] = (byte)((source >> 8) & LowestByteMask);
        }

        /// <summary>
        /// Copies bytes from source unsigned short to destination
        /// </summary>
        /// <param name="source">Source value.</param>
        /// <param name="destination">Destination array. </param>
        /// <param name="destinationStartIndex">Index where data is copied to.</param>
        public static void CopyBytes(
          ushort source,
          byte[] destination,
          int destinationStartIndex)
        {
            destination[destinationStartIndex] = (byte)(source & LowestByteMask);
            destination[destinationStartIndex + 1] = (byte)((source >> 8) & LowestByteMask);
        }
    }
}
